package com.neborosoft.lib.collections.tree;

import com.neborosoft.lib.collections.stack.LimitedStack;

import java.util.HashMap;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: CM
 * Date: 01.12.12
 * Time: 19:15
 * To change this template use File | Settings | File Templates.
 */
public abstract class NTree<T> {
    private LimitedStack< List<T> > elements;
    private LimitedStack<T> parents;
    private int level;
    private LimitedStack<Integer> stepsCountStack;

    public static class State<T>{
        int level;
        LimitedStack<Integer> stepsCountStack;
        LimitedStack< List<T> > elements;
    }

    protected abstract List<T> getChildrenOfParent(T parent, int level, int stepsCount);

    protected int getLevelStepsCountFromParentToChild(T parent, int level){
        return 0;
    }

    protected abstract List<T> getRootItems();

    public State<T> getState(){
        State<T> result = new State<T>();
        result.elements = elements;
        result.level = level;
        result.stepsCountStack = stepsCountStack;

        return result;
    }

    public void restart(List<T> rootItems){
        elements.clear();
        parents.clear();
        elements.push(rootItems);
        level = 0;
        stepsCountStack.clear();
    }

    public void restart(){
        List<T> rootItems = getRootItems();
        restart(rootItems);
    }

    public void restartWithoutLoadingRootElements(){
        int size = elements.getSize();
        elements.popSeveralTimes(size - 1);
        level = 0;
        stepsCountStack.clear();
        parents.clear();
    }

    private void initStacks(int levelsCount){
        elements = new LimitedStack<List<T>>(levelsCount);
        parents = new LimitedStack<T>(levelsCount - 1);
        stepsCountStack = new LimitedStack<Integer>(levelsCount - 1);
    }

    protected NTree(int levelsCount) {
        initStacks(levelsCount);
        restart();
    }

    protected NTree(List<T> rootItems, int levelsCount) {
        if(levelsCount <= 0){
            throw new IllegalArgumentException();
        }

        initStacks(levelsCount);
        restart(rootItems);
    }

    public int getLevel() {
        return level;
    }

    public int getLevelsCount() {
        return elements.getMaxSize();
    }

    public List<T> getElements() {
        return elements.top();
    }

    public boolean canEnterChild(){
        return getLevelsCount() > level + 1;
    }

    public void enterChild(int position){
        if(!canEnterChild()){
            throw new IllegalStateException("can not enter child");
        }

        List<T> current = elements.top();
        T parent = current.get(position);
        int stepsCount = getLevelStepsCountFromParentToChild(parent, level);
        current = getChildrenOfParent(parent, level, stepsCount);
        elements.push(current);
        parents.push(parent);
        level += stepsCount + 1;

        stepsCountStack.push(stepsCount);
    }

    public boolean canExitChild(){
        return level > 0;
    }

    public void exitChild(){
        if(!canExitChild()){
            throw new IllegalStateException("can not exit child");
        }

        int stepsCount = stepsCountStack.pop();
        level -= stepsCount + 1;
        elements.pop();
        parents.pop();
    }

    public void exitToLevel(int level){
        while (getLevel() != level){
            exitChild();
        }
    }

    public void restoreState(State<T> state){
        elements = state.elements;
        level = state.level;
        stepsCountStack = state.stepsCountStack;
    }

    public T getParent(){
        if(!parents.isEmpty()){
            return parents.top();
        } else {
            return null;
        }
    }

    public Integer getStepsLastCount() {
        if(stepsCountStack.isEmpty()){
            return null;
        } else {
            return stepsCountStack.top();
        }
    }

    public int optLastStepsCount(int defaultValue){
        if(stepsCountStack.isEmpty()){
            return defaultValue;
        } else {
            return stepsCountStack.top();
        }
    }
}
